/*******************************************************************************
 * Copyright (c) 2002, 2003 Boris Pruessmann and others. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Common Public License v0.5 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors: Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.core.internal;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.PerforceException;
import net.sourceforge.perforce.core.PerforceSynchronizer;
import net.sourceforge.perforce.core.api.ICountersResult;
import net.sourceforge.perforce.core.api.IDescribeResult;
import net.sourceforge.perforce.core.api.IOpenedResult;
import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.api.IPerforceServerManager;
import net.sourceforge.perforce.core.api.IWhereResult;
import net.sourceforge.perforce.core.resources.IChangelist;
import net.sourceforge.perforce.core.syncinfo.ResourceSyncInfo;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;

/**
 * @version $Revision: 488 $
 * @author <a href="mailto:bpruessmann@users.sourceforge.net">Boris
 *         Pruessmann</a>
 */
public class BackgroundUpdateRunnable implements Runnable {
  // ---------------------------------------------------------------------------
  // Private Variables

  /** Stores the Threadthat is running the monitor. */
  private Thread thread;

  /** Indicates whether the "Background Update Daemon" is running. */
  private boolean running = true;

  /** Maps the servername to its counter value. */
  private Map<String, Integer> counterMap = new HashMap<String, Integer>();

  //----------------------------------------------------------------------------
  // -- Public Methods

  /** @see java.lang.Runnable#run() */
  public void run() {
    thread = Thread.currentThread();

    IPerforceServerManager manager = PerforceCorePlugin.getServerManager();
    while (running) {
      final IPerforceServer[] servers = manager.getPerforceServers();
      for (int i = 0; i < servers.length; i++) {
        final IPerforceServer server = servers[i];

        if (server.shouldIncludeInBackgroundUpdates()) {
          int lastChangelist = getLastChangelistNumber(server);
          int currentChangelist = getCurrentChangelistNumber(server);

          // Collect files that have changed because a changelist was submitted
          Set<IResource> affectedResources = new HashSet<IResource>();

          if (PerforceCorePlugin.getPlugin().shouldMonitorChangelists()) {
            for (int j = lastChangelist + 1; j <= currentChangelist; j++) {
              affectedResources.addAll(getAffectedResources(server, j));
            }
          }

          // Collect files that are opened on this client
          if (PerforceCorePlugin.getPlugin().shouldMonitorOpenedFiles()) {
            affectedResources.addAll(collectOpenedResources(server));
          }

          Map<IResource, ResourceSyncInfo> resourceMap = new HashMap<IResource, ResourceSyncInfo>();
          for (Iterator<IResource> it = affectedResources.iterator(); it.hasNext();) {
            try {
              IResource resource = it.next();
              ResourceSyncInfo[] syncInfo = server.fstat(resource.getLocation().toOSString(), null);
              if (syncInfo != null && syncInfo.length == 1) {
                resourceMap.put(resource, syncInfo[0]);
              }
            } catch (PerforceException e) {
              PerforceCorePlugin.log(e.getStatus());
            }
          }

          updateCache(resourceMap);
        }
      }

      try {
        Thread.sleep(PerforceCorePlugin.getPlugin().getBackgroundUpdateInterval() * 1000);
      } catch (InterruptedException e) {
        // Ignored!
      }
    }
  }

  /** Call this method to start the Background Update Monitor. */
  public void startup() {
    new Thread(this, "Perforce Background Update Monitor").start(); //$NON-NLS-1$
  }

  /** Call this method to shutdown the connection monitor. */
  public void shutdown() {
    running = false;
    try {
      thread.interrupt();
      thread.join();
    } catch (InterruptedException e) {
      // Ignored!
    }
  }

  //----------------------------------------------------------------------------
  // - Private Methods

  /**
   * Returns the number of the changelist we synchronized last.
   * 
   * @param server the perforce server.
   * 
   * @return number of last synchronized changelist.
   */
  private int getLastChangelistNumber(IPerforceServer server) {
    String key = server.getUniqueName();

    Integer result = counterMap.get(key);
    if (result == null) {
      result = new Integer(getCurrentChangelistNumber(server));
      counterMap.put(key, result);
    }

    return result.intValue();
  }

  /**
   * Retrieves the current (server-side) changelist number.
   * 
   * @param server the perforce server.
   * 
   * @return the changelist number.
   */
  private int getCurrentChangelistNumber(IPerforceServer server) {
    try {
      ICountersResult countersResult = server.counters();

      Integer result = countersResult.getCounterValue("change");
      return (result != null) ? result.intValue() : 0;
    } catch (PerforceException e) {
      PerforceCorePlugin.log(e.getStatus());
      return 0;
    }
  }

  /**
   * Returns the resources that are affected by the given changelist.
   * 
   * @param server the perforce server.
   * @param changelist the changelist number.
   * @return set of resources.
   */
  private Set<IResource> getAffectedResources(IPerforceServer server, int changelist) {
    Set<IResource> result = new HashSet<IResource>();
    try {
      IDescribeResult describeResult = server.describe(new Integer(changelist));
      if (describeResult.getStatus().equals(IChangelist.STATUS_PENDING)) {
        return result;
      }

      // Collect depot filenames
      ResourceSyncInfo[] affectedFiles = describeResult.getAffectedFiles();

      // Get resources that are contained in the workspace
      IWorkspaceRoot workspaceRoot = PerforceCorePlugin.getWorkspace().getRoot();
      for (int i = 0; i < affectedFiles.length; i++) {
        try {
          IWhereResult whereResult = server.where(affectedFiles[i].getDepotFileName());

          IPath path = new Path(whereResult.getLocalFilename());
          IResource resource = workspaceRoot.getFileForLocation(path);
          if (resource != null && resource.exists()) {
            result.add(resource);
          }
        } catch (PerforceException e) {
          // Ignored!
        }
      }
    } catch (PerforceException e) {
      PerforceCorePlugin.log(e.getStatus());
    }

    return result;
  }

  /**
   * Call this method to update the persistence cache with the information
   * stored in the given map (IResource -> ResourceSyncInfo).
   * 
   * @param resourceMap the map.
   */
  private void updateCache(Map<IResource, ResourceSyncInfo> resourceMap) {
    if (resourceMap.isEmpty()) {
      return;
    }

    try {
      PerforceSynchronizer.getInstance().beginOperation(null);

      for (Iterator<IResource> it = resourceMap.keySet().iterator(); it.hasNext();) {
        IResource resource = it.next();
        ResourceSyncInfo syncInfo = resourceMap.get(resource);

        PerforceSynchronizer.getInstance().setResourceSync(resource, syncInfo);
      }
    } catch (PerforceException e) {
      PerforceCorePlugin.log(e.getStatus());
    } finally {
      try {
        PerforceSynchronizer.getInstance().endOperation(null);
      } catch (PerforceException e) {
        PerforceCorePlugin.log(e.getStatus());
      }
    }
  }

  /**
   * Collects resource that are open on the current client.
   * 
   * @param server the server.
   * @return set of resources.
   */
  private Set<IResource> collectOpenedResources(IPerforceServer server) {
    Set<IResource> result = new HashSet<IResource>();
    try {
      IOpenedResult[] openedResults = server.opened(false);

      // Collect depot filenames
      String[] filenames = new String[openedResults.length];
      for (int i = 0; i < openedResults.length; i++) {
        filenames[i] = openedResults[i].getDepotFilename();
      }

      // Get resources that are contained in the workspace
      IWorkspaceRoot workspaceRoot = PerforceCorePlugin.getWorkspace().getRoot();
      IWhereResult[] whereResults = server.where(filenames);
      for (int i = 0; i < whereResults.length; i++) {
        IPath path = new Path(whereResults[i].getLocalFilename());
        IResource resource = workspaceRoot.getFileForLocation(path);
        if (resource != null && resource.exists()) {
          result.add(resource);
        }
      }
    } catch (PerforceException e) {
      int statusCode = e.getStatus().getCode();
      if (statusCode != 2010) // File's not in client view
      {
        PerforceCorePlugin.log(e.getStatus());
      }
    }

    return result;
  }
}
